package org.hackystat.ambientdevice.trigger;

import java.util.Collections;
import java.util.List;
import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.ambientdevice.server.jaxb.Project;
import org.hackystat.ambientdevice.server.jaxb.Trigger;
import org.hackystat.sensorbase.client.SensorBaseClient;
import org.hackystat.sensorbase.client.SensorBaseClientException;
import org.hackystat.sensorbase.resource.sensordata.jaxb.Properties;
import org.hackystat.sensorbase.resource.sensordata.jaxb.Property;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
import org.hackystat.utilities.tstamp.Tstamp;

/**
 * Trigger that activated by a certain type of sensordata, which is customizable. 
 * @author Shaoxuan Zhang
 * @author Sonwright M. Gomez
 */
public class SensorDataTrigger extends HackystatTrigger {
  /**
   * Supports serialization.
   */
  private static final long serialVersionUID = 1L;
  /** the SensorBaseClient for retrieving data. */
  private SensorBaseClient client;
  /** the SensorDataType this trigger interested in. */
  private String sdt;
  /** the property within this SensorDataType will be evaluated. */
  private String property;
  /** the current state of this trigger. */
  private boolean currentState = false;
  
  /**
   * @param project the project will be monitored
   * @param trigger the JAXB trigger object that defines this trigger
   * @param client the SensorBaseClient
   */
  public SensorDataTrigger(Project project, Trigger trigger, SensorBaseClient client) {
    super(project, trigger);
    if (trigger.getId() == null) {
      this.setId("SensorData " + trigger.getSDT());
      if (this.getValue() == null) {
        this.setId(this.getId() + " exists");
      }
      else {
        this.setId(this.getId() + " " + this.getValue());
      }
    }
    else {
      this.setId("SensorData " + trigger.getId());
    }
    this.setClient(client);
    this.setSdt(trigger.getSDT());
    this.setProperty(trigger.getProperty());
    /*
    if (!verify()) {
      logger.warning("SDT " + this.getSdt() + " with property = " + this.getProperty() + 
          " and value = " + this.value + " is invalid.");
    }
    */
  }
  /*
  private boolean verify(){
    SensorDataType sensorDataType;
    try {
      sensorDataType = getClient().getSensorDataType(this.getSdt());
    }
    catch (SensorBaseClientException e) {
      logger.warning("Errors when geting SDT from server:" + this.getSdt());
      return false;
    }
    Property property = new Property();
    property.setKey(this.getProperty());
    property.setValue(this.getValue());
    sensorDataType.getProperties().getProperty().contains(property);
    return false;
  }
  */
  /**
   * Return the newest state of this trigger.
   * @return true only when new data is found and the newest one is valid. otherwise, false.
   */
  @Override
  public boolean getState() {
    XMLGregorianCalendar currentTime = Tstamp.makeTimestamp();
    if (this.getLastUpdateTime() == null) {
      logger.fine(this.getId() + ": first time running, searching back for initializing data.");
      try {
        this.setLastUpdateTime(client.getProject(getProject().getOwner(), 
            getProject().getProjectName()).getStartTime());
      }
      catch (SensorBaseClientException e) {
        logger.warning(this.getId() + ": " + e.getMessage());
        return false;
      }
    }
    SensorDataIndex index;
    try {
      index = getClient().getProjectSensorData(this.getProject().getOwner(), 
          this.getProject().getProjectName(), getLastUpdateTime(), currentTime, this.getSdt());
      this.setLastUpdateTime(currentTime);
    }
    catch (SensorBaseClientException e) {
      logger.warning(this.getId() + ": " + e.getMessage());
      return false;
    }

    List<SensorDataRef> sensorDataRefList = index.getSensorDataRef();
    if (sensorDataRefList.isEmpty()) {
      logger.fine(this.getId() + ": no new data found.");
      if (this.getValue() == null) {
        currentState = false;
      }
      return currentState;
    }
    SensorDataRef sensorDataRef = Collections.max(sensorDataRefList, 
                                                    new CompareSensorDataRefByDate());
    SensorData data;
    try {
      data = getClient().getSensorData(sensorDataRef);
    }
    catch (SensorBaseClientException e) {
      logger.warning(e.getMessage());
      return false;
    }
    if (this.getValue() == null) {
      logger.fine(this.getId() + ": new Data retrieved.");
      currentState = true;
      return currentState;
    }
    String result = getPropertyValue(data, this.getProperty());
    logger.fine(this.getId() + ": new Data retrieved: " + this.getProperty() + " = " + result);
    currentState =  this.getValue().equalsIgnoreCase(result);
    return currentState;
  }

  /**
   * Gets the value for the given property name from the <code>Properties</code> object contained
   * in the given sensor data instance.
   * 
   * @param data The sensor data instance to get the property from.
   * @param propertyName The name of the property to get the value for.
   * @return Returns the value of the property or null if no matching property was found.
   */
  public static String getPropertyValue(SensorData data, String propertyName) {
    Properties properties = data.getProperties();
    if (properties != null) {
      List<Property> propertyList = properties.getProperty();
      for (Property property : propertyList) {
        if (property.getKey().equals(propertyName)) {
          return property.getValue();
        }
      }
    }
    return null;
  }
  
  /**
   * @param client the SensorBaseClient to set
   */
  public final void setClient(SensorBaseClient client) {
    this.client = client;
  }
  /**
   * @return the client
   */
  public final SensorBaseClient getClient() {
    return client;
  }
  /**
   * @param sdt the SensorDataType to set
   */
  public final void setSdt(String sdt) {
    this.sdt = sdt;
  }
  /**
   * @return the SensorDataType
   */
  public final String getSdt() {
    return sdt;
  }
  /**
   * @param property the property to set
   */
  public final void setProperty(String property) {
    this.property = property;
  }
  /**
   * @return the property
   */
  public final String getProperty() {
    return property;
  }

}
